/*
 * \brief  XML parser
 * \author Norman Feske
 * \date   2007-08-21
 */

/*
 * Copyright (C) 2007-2009 Norman Feske
 * Genode Labs, Feske & Helmuth Systementwicklung GbR
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

#ifndef _INCLUDE__UTIL__XML_NODE_H_
#define _INCLUDE__UTIL__XML_NODE_H_

#include <util/token.h>

namespace Genode {

	class Xml_node
	{
		class Tag
		{
			private:

				Token _token;
				Token _name;
				bool  _valid;

			public:

				enum Type { START, END, INVALID };

				/**
				 * Constructor
				 *
				 * \param start  first token of the tag
				 *
				 * At construction time, the validity of the tag is
				 * checked. A valid tag consists of both tag delimiters
				 * containing an identifier with an optional '/'
				 * prefix, for example '<tag>' or '</tag>'.
				 */
				Tag(Token start) : _token(start), _valid(false)
				{
					if (_token[0] != '<')
						return;

					if (_token.next().type() != Token::IDENT && _token.next()[0] != '/')
						return;

					_name = _token.next()[0] == '/' ? _token.next().next() : _token.next();
					if (_name.type() != Token::IDENT)
						return;

					if (_name.next()[0] != '>')
						return;

					_valid = true;
				}

				/**
				 * Default constructor produces invalid Tag
				 */
				Tag() : _valid(false) { }

				/**
				 * Return type of tag
				 */
				Type type()
				{
					if (!_valid)
						return INVALID;

					if (_token.next()[0] == '/')
						return END;

					return START;
				}

				/**
				 * Return first token of tag
				 */
				Token token() { return _token; }

				/**
				 * Return name of tag
				 */
				Token name() { return _name; }

				/**
				 * Return token after the closing tag delimiter
				 */
				Token next_token() { return _name.next().next(); }
		};

		class Comment
		{
			private:

				Token _next;
				bool  _valid;

				/**
				 * Check for comment-start tag
				 */
				bool _is_start(Token t)
				{
					const char tag[] = {'<', '!', '-', '-', 0};
					for (int i = 0; tag[i]; t = t.next(), i++)
						if (t[0] != tag[i])
							return false;
					return true;
				}

				/**
				 * Check for comment-end tag
				 */
				bool _is_end(const char *s)
				{
					const char tag[] = {'-', '-', '>', 0};
					for (int i = 0; tag[i]; i++)
						if (s[i] != tag[i])
							return false;
					return true;
				}

			public:

				/**
				 * Constructor
				 *
				 * \param start  first token of the comment tag
				 */
				Comment(Token t) : _valid(false)
				{
					/* check for comment-start tag */
					if (!_is_start(t))
						return;

					/* search for comment-end tag */
					char *s = t.start();
					for ( ; !_is_end(s); s++);
					t = Token(s);

					if (t.type() == Token::END)
						return;

					_next  = t.next().next().next();
					_valid = true;
				}

				/**
				 * Default constructor produces invalid Comment
				 */
				Comment() : _valid(false) { }

				/**
				 * Return true if comment is valid
				 */
				bool valid() { return _valid; }

				/**
				 * Return token after the closing comment delimiter
				 */
				Token next_token() { return _next; }
		};

		private:

			int _num_sub_nodes;
			Tag _start_tag;
			Tag _end_tag;

			/**
			 * Search for end tag of XML node and initialize _num_sub_nodes
			 *
			 * \return  end tag or invalid tag
			 *
			 * The function searches for a end tag that matches the same
			 * depth level and the same name as the start tag of the XML
			 * node. If the XML structure is invalid, the search results
			 * is an invalid Tag.
			 *
			 * During the search, the function also counts the number of
			 * immediate sub nodes.
			 */
			Tag _init_end_tag(const char *tag_name)
			{
				if (!tag_name) return Tag();

				int   depth = 1;
				Token curr_token = _start_tag.next_token();

				while (curr_token.type() != Token::END) {

					/* eat XML comment */
					Comment curr_comment(curr_token);
					if (curr_comment.valid()) {
						curr_token = curr_comment.next_token();
						continue;
					}

					/* skip all tokens that are no tags */
					Tag curr_tag(curr_token);
					if (curr_tag.type() == Tag::INVALID) {
						curr_token = curr_token.next();
						continue;
					}

					/* count sub nodes at depth 1 */
					if (depth == 1 && curr_tag.type() == Tag::START)
						_num_sub_nodes++;

					/* keep track of the current depth */
					depth += (curr_tag.type() == Tag::START);
					depth -= (curr_tag.type() == Tag::END);

					/* within sub nodes, continue after current token */
					if (depth > 0) {

						/* continue search with token after current tag */
						curr_token = curr_tag.next_token();
						continue;
					}

					/* reaching the same depth as the start tag */
					const char *start_name = _start_tag.name().start();
					size_t      start_len  = _start_tag.name().len();
					const char *curr_name  =   curr_tag.name().start();
					size_t      curr_len   =   curr_tag.name().len();

					/* on mismatch of start tag and end tag, return invalid tag */
					if (start_len != curr_len
					 || strcmp(start_name, curr_name, curr_len))
						return Tag();

					/* end tag corresponds to start tag */
					return curr_tag;
				}
				return Tag();
			}

			/**
			 * Find next non-whitespace and non-comment token
			 */
			static Token eat_whitespaces_and_comments(Token t)
			{
				while (true) {

					t = t.eat_whitespace();

					/* eat comment */
					Comment comment(t);
					if (comment.valid()) {
						t = comment.next_token();
						continue;
					}

					break;
				}
				return t;
			}

		public:

			/*********************
			 ** Exception types **
			 *********************/

			class Invalid_syntax      : public Exception { };
			class Nonexistent_subnode : public Exception { };


			/**
			 * Constructor
			 *
			 * The constructor validates if the start tag has a
			 * matching end tag of the same depth and counts
			 * the number of immediate sub nodes.
			 */
			Xml_node(const char *addr) :
				_num_sub_nodes(0),
				_start_tag(eat_whitespaces_and_comments(Token(addr))),
				_end_tag(_init_end_tag(_start_tag.name().start()))
			{
				if (_end_tag.type() != Tag::END)
					throw Invalid_syntax();
			}

			/**
			 * Request type name of XML node as null-terminated string
			 */
			void type_name(char *dst, size_t max_len) {
				_start_tag.name().string(dst, max_len); }

			/**
			 * Return true if tag is of specified type
			 */
			bool is_type(const char *type) {
				return (!strcmp(type, _start_tag.name().start(),
				                      _start_tag.name().len())
				      && strlen(type) == _start_tag.name().len()); }

			/**
			 * Request content of XML node as null-terminated string
			 */
			void content(char *dst, size_t max_len) {
				strncpy(dst, content_addr(), min(content_size() + 1, max_len)); }

			/**
			 * Return begin of node including the start tag
			 */
			char *addr() { return _start_tag.token().start(); }

			/**
			 * Return size of node including start and end tags
			 */
			size_t size() {
				return (size_t)(_end_tag.next_token().start() - addr()); }

			/**
			 * Return begin of node content as an opaque string
			 *
			 * Note that the returned string is not null-terminated as it
			 * points directly into a sub range of the unmodified Xml_node
			 * address range.
			 */
			char *content_addr() {
				return _start_tag.next_token().start(); }

			/**
			 * Return size of node content
			 */
			size_t content_size() {
				return (size_t)(_end_tag.token().start() - content_addr()); }

			/**
			 * Return the number of the XML node's immediate sub nodes
			 */
			int num_sub_nodes() {
				return _num_sub_nodes; }

			/**
			 * Return XML node followed by the current one
			 */
			Xml_node next()
			{
				Token after_node = _end_tag.next_token();
				after_node = eat_whitespaces_and_comments(after_node);
				return Xml_node(after_node.start());
			}

			/**
			 * Return sub node with specified index
			 *
			 * \throw  Invalid_syntax       if subnode is malformed
			 * \throw  Nonexistent_subnode  if no such subnode exists
			 */
			Xml_node sub_node(int idx)
			{
				/* look up node at specified index */
				Xml_node curr_node(content_addr());
				try {
					while (idx--)
						curr_node = curr_node.next();
				} catch (Invalid_syntax) { }

				/* if XML node at specified index is valid, return */
				if (idx <= 0)
					return curr_node;

				throw Nonexistent_subnode();
			}

			/**
			 * Return first sub node that matches the specified type
			 *
			 * \throw Nonexistent_subnode  if no such subnode exists
			 */
			Xml_node sub_node(const char *type)
			{
				/* search for sub node of specified type */
				try {
					Xml_node curr_node(content_addr());
					for ( ; true; curr_node = curr_node.next())
						if (curr_node.is_type(type))
							return curr_node;
				} catch (...) {
					throw Nonexistent_subnode();
				}
			}

			/**
			 * Read numeric size_t value from XML node
			 *
			 * \param out_res  resulting size_t value
			 *
			 * \retval  0  success
			 * \retval <0  error code
			 */
			int read_size_t(size_t *out_res)
			{
				if (ascii_to_size(content_addr(), out_res) != content_size())
					return -1;

				return 0;
			}
	};
}

#endif /* _INCLUDE__UTIL__XML_NODE_H_ */
